import logging

from adrf.views import APIView as AsyncAPIView
from asgiref.sync import sync_to_async
from channels.layers import get_channel_layer
from django.core.exceptions import ValidationError
from rest_framework import serializers
from rest_framework.exceptions import PermissionDenied
from rest_framework.response import Response

from sysreptor.pentests.models.collab import CollabEvent


class ConsumerHttpFallbackSerializer(serializers.Serializer):
    version = serializers.FloatField(min_value=0)
    client_id = serializers.CharField()
    messages = serializers.ListField(child=serializers.JSONField())

    def validate_client_id(self, value):
        user = self.context["request"].user
        if user and user.is_anonymous:
            user = None

        if not value or (user and not value.startswith(str(user.id))) or (not user and not value.startswith('anonymous')):
            raise ValidationError(f"Invalid client_id: expected '{getattr(user, 'id', 'anonymous')}/...' got '{value}'")
        return value


class ConsumerHttpFallbackView(AsyncAPIView):
    schema = None
    consumer_class = None
    permission_classes = []  # Permission check is handled in the consumer

    async def get_consumer(self, action=None, client_id=None):
        # Initialize consumer
        consumer = self.consumer_class()
        consumer.scope = {
            "user": self.request.user,
            "session": self.request.session,
            "path": self.request.path,
            "url_route": {"kwargs": self.kwargs, "args": self.args},
            "client_id": client_id,
        }
        consumer.related_id = await consumer.get_related_id()
        consumer.channel_layer = get_channel_layer(consumer.channel_layer_alias)

        # Check permissions
        if not (await sync_to_async(consumer.has_permission)(action=action)):
            raise PermissionDenied()
        return consumer

    async def get(self, request, *args, **kwargs):
        consumer = await self.get_consumer(action="read")
        data = await consumer.get_initial_message()
        return Response(data=data)

    async def post(self, request, *args, **kwargs):
        serializer = ConsumerHttpFallbackSerializer(data=request.data, context={"request": request})
        serializer.is_valid(raise_exception=True)
        data = serializer.data
        version = data["version"]

        consumer = await self.get_consumer(action="write", client_id=data["client_id"])

        # Dispatch incoming messages
        error = None
        for msg in request.data.get("messages", []):
            msg_type = msg.get("type")
            if msg_type == "collab.ping":
                continue  # Ignore pings, they are only relevant for websocket keepalive
            try:
                await consumer.receive_json(msg)
            except ValidationError:
                continue
            except Exception as ex:
                logging.exception(ex)
                # Raise error after processing all events
                error = ex
        if error:
            raise error

        # Get events since version (including responses to incoming messages)
        events = CollabEvent.objects \
            .filter(version__gt=version) \
            .filter(related_id=consumer.related_id) \
            .order_by("created")
        events = consumer.filter_path(events)
        events = [e.to_dict() async for e in events]
        events = await consumer.filter_events(events)

        return Response(
            data={
                "version": max([version] + [e['version'] for e in events]),
                "messages": events,
                "clients": await sync_to_async(consumer.get_client_infos)(),
            },
        )


class ReadonlyConsumerHttpFallbackView(ConsumerHttpFallbackView):
    permission_classes = []  # Disable permission checks for read-only access

    async def get_consumer(self, action=None, client_id=None):
        if action != 'read':
            raise PermissionDenied("This endpoint is read-only")
        return await super().get_consumer(action=action, client_id=client_id)

    async def get(self, request, *args, **kwargs):
        response = await super().get(request, *args, **kwargs)
        if isinstance(response.data.get('permissions', {}).get('write'), bool):
            response.data['permissions']['write'] = False
        return response
